#pragma once

#ifndef _HUNTERAPI_H
#define _HUNTERAPI_H

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <string>
#include <sqlite3.h>
#include <sstream>
#include <cmath>
#include <ctime>
#include <map>
#include <algorithm>

#include "inttostr.h"
#include "md5.h"
#include "hextochr.hpp"

using namespace std;

namespace HunterAPI
{

	string md5(string &s)
	{
		MD5 md;
		char* tmp = new char[s.length() + 1];
		tmp = strcpy(tmp, s.c_str());
		return md.digestString(tmp);
	}
	
	map <string, string> parseHTTPinput(string o)
	{
		map <string, string> result;
		pair <string, string> cur;
		int i = 0;
		while (i < o.length())
		{
			while (o[i] != '=')
			{
				if (o[i] == '+')
					cur.first += ' ', i++;
				else if (o[i] == '%')
				{
					cur.first += hextochr(o.substr(i, 3));
					i += 3;
				}
				else
					cur.first += o[i++];
			}
			i++;
			while (i < o.length() && o[i] != '&')
			{
				if (o[i] == '+')
					cur.second += ' ', i++;
				else if (o[i] == '%')
				{
					cur.second += hextochr(o.substr(i, 3));
					i += 3;
				}
				else
					cur.second += o[i++];
			}
			i++;
			result[cur.first] = cur.second;
			cur = make_pair("", "");
		}
		return result;
	}
	
	class Player
	{
	public:
		int id;
		string username;
		string pass;
		string stats;
		int engaged;
		
		double rating;
		void secure(void)
		{
			pass = md5(pass);
		}
		Player (void)
		{
			id = engaged = 0;
		}
	};
	
	class PlayerHeavy: public Player
	{
	public:
		string 
			p_bio_firstn,
			p_bio_lastn,
			p_bio_midn,
			p_bio_age,
			p_bio_city,
			p_bio_region,
			p_bio_street,
			p_bio_univer,
			p_bio_grade,
			p_bio_spec,
			p_bio_photo1,
			p_bio_photo2,
			p_bio_misc;
	};

	class Hunt
	{
	public:
		int id;
		pair <int, int> guys;
		pair <string, string> codes;
		pair <int, int> classes;
		pair <string, string> comments;
		int result;
		string date;
	};
	
	class BlogEntry
	{
	public:
		int id;
		string date;
		string header, text;
		int author_id;
	};
	
	string getNewCode(void)
	{
		string result = "AAAAA";
		for (int i = 0; i < result.length(); i++)
		{
			int c = floor(((double)rand()) / RAND_MAX * ('Z' - 'A'));
			result[i] += c;
		}
		return result;
	}
	
	vector <pair <string, string> > ans;	
	static int callback(void *NotUsed, int argc, char **argv, char **azColName)
	{
		int i;
		for (i = 0; i < argc; i++)
		{
			//printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
			ans.push_back(make_pair(azColName[i], argv[i] ? argv[i] : "NULL"));
		}
		return 0;
	}


	class HiveMind
	{
	public:
		// constants
		double default_rating;
		// players
		bool
			loginPlayer(string form_o, string &err, int &id)
		{
			// parsing
			string u, p = u = "";
			
			map <string, string> parsed = parseHTTPinput(form_o);
			u = parsed["u"], p = md5(parsed["p"]);
			
			// query
			ans.clear();
			
			ostringstream ss;
			char* emsg;
			ss << "select p_id from Players where p_username = \'" << u << "\' and p_password = \'" << p << "\'"; // UNSECURE, TODO
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "Login failed, " << emsg;
				err = emsg;
				return true;
			}
			
			if (ans.empty())
			{
				err = "No such u/p: [" + u + "], [" + p + "]"; 
				return true;
			}
			
			id = strtoint(ans[0].second);
			return false;
		}
		bool
			bakeCookie(int id, string cookie)
		{
			ans.clear();
			ostringstream ss;
			char* emsg;
			
			ss << "insert into Cookies values(" << id << ", '" << cookie << "')";
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "bakeCookie error: " << emsg;
				return true;
			}
			
			return false;
		}
		bool
			verifyCookie(string cookie, int &id)
		{
			ans.clear();
			ostringstream ss;
			char* emsg;
			
			ss << "select c_id from Cookies where c_cookie like '" << cookie << "'";
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "verifyCookie error: " << emsg;
				return true;
			}
			
			if (ans.empty())
				id = -1;
			else
				id = strtoint(ans[0].second);
				
			return false;
		}
		bool
			dropCookies(int p_id)
		{
			ans.clear();
			ostringstream ss;
			char* emsg;
			
			ss << "delete from Cookies where c_id = " << p_id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "dropCookies error: " << emsg;
				return true;
			}
			
			return false;
		}
		bool	
			createNewPlayer(string p_name, string p_pass, Player &result)
		{
			ans.clear();
			
			ostringstream ss;
			char* emsg;
			ss << "insert into Players values(";
			if (sqlite3_exec(db, "select p_id from Players order by p_id desc limit 1", callback, 0, &emsg))
			{
				cerr << "createNewPlayer: select top p_id error: " << emsg << "\n";
				return true;
			}
			
			p_pass = md5(p_pass);
			
			int id = strtoint(ans.front().second) + 1; 
			ss << id << ", "
			   << "'" << p_name  << "', '" << p_pass << "', "
			   << default_rating << ", NULL, '', '', '', '', '', '', '', '', '', '', '', '', '')";
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "createNewPlayer: insert error " << emsg << ": " << ss.str() << "\n";
				return true;
			}
			
			getPlayerById(id, result);
			return false;
			 
		}
		
		bool
			getIdleIds(vector <int> &result)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select p_id from Players where p_engaged is null";
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getIdleIds error " << emsg << "\n";
				return true;
			}			
			
			for (int i = 0; i < ans.size(); i++)
				result.push_back(strtoint(ans[i].second));
				
			return false;
		}
		bool
			getTopNPlayers(vector <Player> &result, int n = -1)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select * from Players order by p_rating desc";
			if (n != -1)
				ss << " limit " << n; 
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getTopNPlayers error " << emsg << "\n";
				return true;
			}
			
			for (int i = 0; i < ans.size();)
			{
				Player p;
				p.id = strtoint(ans[i++].second);
				p.username = ans[i++].second;
				p.pass = ans[i++].second;
				p.stats = ans[i++].second;
				p.rating = strtoflt(ans[i++].second);
				p.engaged = strtoint(ans[i++].second);
				i += 13; // heavy params skipping
				
				result.push_back(p);
			}

			return false;

		}
		
		bool
			insertPlayerHeavy(PlayerHeavy &p)
		{
				ans.clear();
				
				ostringstream ss;
				char* emsg;
				ss << "insert into Players values(";
				if (sqlite3_exec(db, "select p_id from Players order by p_id desc limit 1", callback, 0, 	&emsg))
				{
					cerr << "createNewPlayer: select top p_id error: " << emsg << "\n";
					return true;
				}
				int id = strtoint(ans.front().second) + 1; 
				
				ss << id << ", "
				   << "'" << p.username  << "', '" << p.pass << "', "
				   << "'" << p.stats << "', "
				   << p.rating << ", NULL, "
				   << "'" << p.p_bio_firstn << "', '" 
				   << p.p_bio_lastn  << "', '"
				   << p.p_bio_midn   << "', '"
				   << p.p_bio_age    << "', '"
				   << p.p_bio_city   << "', '"
				   << p.p_bio_region << "', '"
				   << p.p_bio_street << "', '"
				   << p.p_bio_univer << "', '"
				   << p.p_bio_grade  << "', '"
				   << p.p_bio_spec   << "', '"
				   << p.p_bio_photo1 << "', '"
				   << p.p_bio_photo2 << "', '"
				   << p.p_bio_misc   << "')";
				if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
				{
					cerr << "createNewPlayer: insert error " << emsg << ": " << ss.str() << "\n";
					return true;
				}
				
				return false;
			}

		bool
			dropPlayer(Player &p)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "delete from Players where p_id = " << p.id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "dropPlayer: delete error " << emsg << "\n";
				return true;
			}
			
			return false;
		}

		bool
			getLastNHunts(vector <Hunt> &result, int n = -1)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select * from Hunts order by h_date limit " << n;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getLastNHunts: select error " << emsg << "\n";
				return true;
			}
			
			for (int i = 0; i < ans.size();)
			{	
				Hunt h;
				h.id = strtoint(ans[i++].second);
				h.guys.first = strtoint(ans[i++].second);
				h.codes.first = ans[i++].second;
				h.classes.first = strtoint(ans[i++].second);
				h.comments.first = ans[i++].second;
				h.guys.second = strtoint(ans[i++].second);
				h.codes.second = ans[i++].second;
				h.classes.second = strtoint(ans[i++].second);
				h.comments.second = ans[i++].second;
				h.result = strtoint(ans[i++].second);
				h.date = ans[i++].second;
				result.push_back(h);
			}
			
			return false;
			
		}
		bool
			getLastNHunts(int p_id, vector <Hunt> &result, int n = -1)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select * from Hunts where h_hunter = " << p_id << " or h_prey = " << p_id << " limit " << n;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getLastNHunts: select error " << emsg << "\n";
				return true;
			}
			
			size_t i = 0;
			while (i < ans.size())
			{	
				Hunt h;
				h.id = strtoint(ans[i++].second);
				h.guys.first = strtoint(ans[i++].second);
				h.codes.first = ans[i++].second;
				h.classes.first = strtoint(ans[i++].second);
				h.comments.first = ans[i++].second;
				h.guys.second = strtoint(ans[i++].second);
				h.codes.second = ans[i++].second;
				h.classes.second = strtoint(ans[i++].second);
				h.comments.second = ans[i++].second;
				h.result = strtoint(ans[i++].second);
				h.date = ans[i++].second;
				result.push_back(h);
			}
			
			return false;
		}
		
		bool
			giveClass(int p_id)
		{
			// TODO
			return 1;
		}
		
		bool countHuntsOfPair(int id1, int id2)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select count(h_id) from Hunts where h_hunter = " << id1 << " and h_prey = " << id2 << " or h_hunter = " << id2 << " and h_prey = " << id1;
			
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "countHuntsOfPair error " << emsg;
				return true;
			}
			
			if (ans.empty())
				return 0;
			return strtoint(ans[0].second);
		}
		bool
			getPlayerById(int p_id, Player &result)
		{
			PlayerHeavy p;
			if (getPlayerByIdH(p_id, p))
				return true;
				
			result.id = p.id;
			result.username = p.username;
			result.pass = p.pass;
			result.stats = p.stats;
			result.engaged = p.engaged;
			result.rating = p.rating;
			return false;
		}
		bool
			getPlayerByIdH(int p_id, PlayerHeavy &result)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select * from Players where p_id = " << p_id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getPlayerById error " << emsg << "\n";
				return true;
			}
			
			if (ans.size() == 0)
			{
				cerr << "ans.size() error\n";
				return true;
			}
			int i = 0;
			result.id = strtoint(ans[i++].second);
			result.username = ans[i++].second;
			result.pass = ans[i++].second;
			result.stats = ans[i++].second;
			result.rating = strtoflt(ans[i++].second);
			result.engaged = strtoint(ans[i++].second);
			result.p_bio_firstn = ans[i++].second,
			result.p_bio_lastn = ans[i++].second,
			result.p_bio_midn = ans[i++].second,
			result.p_bio_age = ans[i++].second,
			result.p_bio_city = ans[i++].second,
			result.p_bio_region = ans[i++].second,
			result.p_bio_street = ans[i++].second,
			result.p_bio_univer = ans[i++].second,
			result.p_bio_grade = ans[i++].second,
			result.p_bio_spec = ans[i++].second,
			result.p_bio_photo1 = ans[i++].second,
			result.p_bio_photo2 = ans[i++].second,
			result.p_bio_misc = ans[i++].second;

			return false;				
		}
		
		bool
			getRatingById(int p_id)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select p_rating from Players where p_id = " << p_id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getRatingById error " << emsg << "\n";
				return true;
			}
			
			if (ans.empty())
			{
				cerr << "no such Player with id = " << p_id;
				return true;
			}
			
			return strtoflt(ans[0].second);
		}

		// hunts
		bool
			createNewHunt(Hunt &tar)
		{
			ans.clear();

			ostringstream ss;
			char* emsg;
			ss << "insert into Hunts values(";
			if (sqlite3_exec(db, "select h_id from Hunts order by h_id desc limit 1", callback, 0, &emsg))
			{
				cerr << "submitNewHunt: select error " << emsg << "\n";
				return true;
			}
			string new_id = inttostr(strtoint(ans[0].second) + 1);
			ss << new_id << ", " << tar.guys.first << ", '" << tar.codes.first << "', " << tar.classes.first << ", '', " << tar.guys.second << ", '" << tar.codes.second << "', " << tar.classes.second << ", '', 0, '" << tar.date << "')";
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "submitNewHunt: insert error " << emsg << "\n";
				return true;
			}
			
			ostringstream ss2;
			ss2 << "update Players set p_engaged = " << new_id << " where p_id = " << tar.guys.first << " or p_id = " << tar.guys.second << "\n";
			if (sqlite3_exec(db, ss2.str().c_str(), callback, 0, &emsg))
			{
				cerr << "submitNewHunt: update error " << emsg << "\n";
				return true;
			}
			
			return false;
		}

		bool
			closeHunt(Hunt &tar, int result)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss1;
			ss1 << "update Hunts set h_result = " << result << " where h_id = " << tar.id;
			if (sqlite3_exec(db, ss1.str().c_str(), callback, 0, &emsg))
			{
				cerr << "closeHunt error " << emsg;
				return true;
			}
			
			ostringstream ss2;
			ss2 << "update Players set p_engaged = NULL where p_id = " << tar.guys.first << " or p_id = " << tar.guys.second;
			if (sqlite3_exec(db, ss2.str().c_str(), callback, 0, &emsg))
			{
				cerr << "closeHunt error " << emsg;
				return true;
			}
			
			// calculate rating
			Player p;
			ostringstream ss3;
			ss3 << "update Players set p_rating = p_rating + 1 where p_id = " << ((result == 1) ? tar.guys.first : tar.guys.second) << ";update Players set p_rating = p_rating - 1 where p_id = " << ((result == 1) ? tar.guys.second : tar.guys.first);
			if (sqlite3_exec(db, ss3.str().c_str(), callback, 0, &emsg))
			{
				cerr << "closeHunt error " << emsg;
				return true;
			}
			
			return false;
		}
		
		bool
			banPlayer(Player &p)
		{
			ans.clear();
			char* emsg;
			ostringstream ss;
			ss << "update Players set p_password = 'banned' where p_id = " << p.id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "banPlayer error " << emsg;
				return true;
			}
			
			return false;
		}
		
		bool
			changeRepSafely(Player &from, Player &to, int value, string &text)
		{
			ans.clear();
			ostringstream ss;
			char* emsg;
			
			ss << "select cm_value from Commends where cm_from = " << from.id << " and cm_to = " << to.id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "changeRepSafely error in select " << emsg;
				return true;
			}
			
			if (ans.empty())
			// safety
			{
				ostringstream ss2;
				ss << "insert into Commends values (" << from.id << ", " << to.id << ", " << value << ", '" << text << "')";
				if (sqlite3_exec(db, ss2.str().c_str(), callback, 0, &emsg))
				{
					cerr << "changeRepSafely error in insert " << emsg;
					return true;
				}
			}
			
			return false;
		}
		bool
			getPlayerRep(Player &pl, int &result)
		{
			ans.clear();
			
			ostringstream ss;
			char* emsg;
			
			ss << "select cm_value from Commends where cm_to = " << pl.id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getPlayerRep error " << emsg;
				return true;
			}
			
			result = 0;
			for (int i = 0; i < ans.size(); i++)
			{
				result += strtoint(ans[i].second);
			}
			
			return false;
		}

		bool
			getHuntById(int h_id, Hunt &result)
		{
			ans.clear();
			
			char* emsg;
			ostringstream ss;
			ss << "select * from Hunts where h_id = " << h_id;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getLastNHunts: select error " << emsg << "\n";
				return true;
			}		
			
			Hunt h;
			int i = 0;
			h.id = strtoint(ans[i++].second);
			h.guys.first = strtoint(ans[i++].second);
			h.codes.first = ans[i++].second;
			h.classes.first = strtoint(ans[i++].second);
			h.comments.first = ans[i++].second;
			h.guys.second = strtoint(ans[i++].second);
			h.codes.second = ans[i++].second;
			h.classes.second = strtoint(ans[i++].second);
			h.comments.second = ans[i++].second;
			h.result = strtoint(ans[i++].second);
			h.date = ans[i++].second;
			result = h;
		}
		
		// blog
		bool
			getLastNBlogEntries(int n, vector <BlogEntry> &result)
		{
			result.clear();
			ans.clear();
			ostringstream ss;
			char* emsg;
			
			ss << "select * from BlogEntries order by be_date desc limit " << n;
			if (sqlite3_exec(db, ss.str().c_str(), callback, 0, &emsg))
			{
				cerr << "getLastNBlogEntries error: " << emsg;
				return true;
			}
			
			if (ans.size() % 5 != 0)
				return true;
				
			for (int i = 0; i < ans.size(); )
			{
				BlogEntry be;
 				 be.id        = strtoint(ans[i++].second);
				 be.date      = ans[i++].second;
				 be.header    = ans[i++].second;
				 be.text      = ans[i++].second;
				 be.author_id = strtoint(ans[i++].second);
				result.push_back(be);
			}
			
			return false;
		}

		// sys
		
		string
			justAsk(string &qry, vector <pair <string, string> > &result)
		{
			ans.clear();
			char* emsg;
			if (sqlite3_exec(db, qry.c_str(), callback, 0, &emsg))
			{
				return string(emsg);
			}
			
			result = ans;
			return "";
		}
		HiveMind(string path)
		{	
			db = NULL;
			default_rating = 10.0;
			if (sqlite3_open(path.c_str(), &db))
				cerr << "Can't open database '" << path << "'\n", db = NULL;
			srand(time(0));
		}
		~HiveMind()
		{
			sqlite3_close(db);
		}		
		
	private:		
		sqlite3 *db;
	};
}



#endif
